using Game.Networking.Events;
using SMGCore.EventSys;
using System;
using System.Collections.Generic;
using UnityEngine;


namespace Game.Networking.Server {
	public class ServerEntityController : ServerSideController<ServerEntityController>, IEntityController {
		public const float  NET_TICK_TIME = 0.1f;
		public const string StateFileName = "entities.info";

		EntityControllerData _state;

		//TODO: класс для более удобной работой со списком сущностей: Фильтрация по типу, по наличию наблюдателей, по активности, етц.
		Dictionary<uint, Entity>                      _allEntities            = new Dictionary<uint, Entity>(512);
		Dictionary<ClientState, PlayerEntityObserver> _netObservers           = new Dictionary<ClientState, PlayerEntityObserver>();
		Dictionary<uint, Queue<ArraySegment<byte>>>   _entityMessageSendQueue = new Dictionary<uint, Queue<ArraySegment<byte>>>();
		Dictionary<uint, int>                         _observerCountTracker   = new Dictionary<uint, int>();

		List<uint>   _entityDestroyQueue = new List<uint>();
		List<Entity> _entitySpawnQueue   = new List<Entity>();

		double _lastTickTime    = 0f;
		double _lastNetSendTime = -0.03f;

		bool _entityListLock;

		List<Entity> _entityCreateQueue = new List<Entity>();

		public ServerEntityController(ServerGameManager owner) : base(owner) { }

		public override void Init() {
			base.Init();
			_state = new EntityControllerData();

			EventManager.Subscribe<OnClientConnected>(this, OnClientConnected);
			EventManager.Subscribe<OnClientDisconnected>(this, OnClientDisconnected);
		}

		public override void Load() {
			base.Load();
			var loaded = ServerSaveLoadController.Instance.LoadSaveFile<EntityControllerData>(StateFileName);
			if ( loaded != null ) {
				_state = loaded;
				Debug.Log($"loaded {loaded.EntityIndex} :count {loaded.EntityStates.Count}");
			}
		}

		public override void PostLoad() {
			base.PostLoad();
			foreach ( var eState in _state.EntityStates ) {
				CreateNewEntity(eState, false, null);
			}
		}

		public override void Save() {
			base.Save();
			var allEntityList = new List<EntityState>(_allEntities.Count);
			foreach ( var item in _allEntities ) {
				if ( !item.Value ) {
					continue;
				}
				var save = item.Value.Save();
				allEntityList.Add(save);
			}
			_state.EntityStates = allEntityList;
			ServerSaveLoadController.Instance.SaveFileToDisk(StateFileName, _state);
		}

		public override void Reset() {
			base.Reset();
			EventManager.Unsubscribe<OnClientConnected>(OnClientConnected);
			EventManager.Unsubscribe<OnClientDisconnected>(OnClientDisconnected);
		}

		public override void Update() {
			base.Update();
			Tick();
			_lastTickTime = ServerGameManager.Time;
			var time = ServerGameManager.Time;
			if ( time > _lastNetSendTime + NET_TICK_TIME ) {
				NetTick();
				_lastNetSendTime = time;
			} else {
				SendQueued();
			}

			UpdateEntityChangeLists();
		}

		void CheckEntityCreateQueue() {
			foreach ( var e in _entityCreateQueue ) {
				if ( _allEntities.ContainsKey(e.ID) ) {
					continue;
				}
				_allEntities.Add(e.ID, e);
			}
			_entityCreateQueue.Clear();
		}

		//TODO: cache
		public void GetEntitiesOfType(EntityType type, List<Entity> outputList) {
			foreach ( var e in _allEntities ) {
				if ( e.Value.EntityType == type ) {
					outputList.Add( e.Value );
				}
			}
		}

		public Entity GetEntityById(uint id) {
			if ( !_allEntities.TryGetValue(id, out var e) ) {
				return null;
			}
			return e;
		}

		public void CallRPCOnClient(Entity e, byte rpcId, byte[] args) {
			if ( e == null || e.ID == 0 ) {
				return;
			}
			var message = ServerController.Instance.GetMessageBytes(ServerPacketID.EnityRPC, new S_EntityRPCMessage() { ID = e.ID, RPCID = rpcId, Data = args });
			AddRawMessageToSendQueue(e.ID, message);
		}

		public void SendSyncVarUpdate(Entity e, byte varId, byte[] args) {
			var message = ServerController.Instance.GetMessageBytes(ServerPacketID.EntitySyncVarChange, new S_EntitySyncVarMessage() { ID = e.ID, VarId = varId, Data = args });
			AddRawMessageToSendQueue(e.ID, message);
		}

		public bool HasNetworkObserver(uint id) {
			if ( _observerCountTracker.ContainsKey(id) ) {
				return _observerCountTracker[id] > 0;
			}
			return false;
		}

		void Tick() {
			var curTime = ServerGameManager.Time;
			var deltaTime = (float) ( curTime - _lastTickTime);
			_entityListLock = true;
			foreach ( var p in _allEntities ) {
				p.Value.Tick((float) curTime, deltaTime);
			}
			_entityListLock = false;
			CheckEntityCreateQueue();
		}

		void NetTick() {
			_entityListLock = true;
			foreach ( var p in _allEntities ) {
				var e = p.Value;
				if ( e && e.IsEntityAlive ) {
					e.NetTick();
				}			
			}
			_entityListLock = false;
			CheckEntityCreateQueue();
		}

		void SendQueued() {
			foreach ( var q in _entityMessageSendQueue ) {
				if ( q.Value.Count == 0 ) {
					continue;
				}
				var count = Mathf.Min(q.Value.Count, 8);
				for ( int i = 0; i < count; i++ ) {
					var msg = q.Value.Dequeue();
					if ( !_observerCountTracker.TryGetValue(q.Key, out var trackerCount) || trackerCount <= 0 ) {
						continue;
					}
					SendEntityMessageToObservers(q.Key, msg);
				}
			}
		}

		void SendEntityMessageToObservers(uint id, ArraySegment<byte> message) {
			var sc = ServerController.Instance;
			foreach ( var item in _netObservers ) {
				if ( item.Value.Entities.ContainsKey(id) ) {
					sc.SendPreformattedMessage(item.Key, message);
				}
			}
		}

		void UpdateEntityChangeLists() {
			var sc = ServerController.Instance;

			foreach ( var uid in _entityDestroyQueue ) {
				if ( _observerCountTracker.ContainsKey(uid) ) {
					_observerCountTracker.Remove(uid);
				}
				if ( _entityMessageSendQueue.ContainsKey(uid) ) {
					_entityMessageSendQueue.Remove(uid);
				}
				if ( !_allEntities.TryGetValue(uid, out var entity) ) {
					continue;
				}
				_allEntities.Remove(uid);
				var message = sc.GetMessageBytes(ServerPacketID.DespawnEntity, new S_DespawnEntityMessage { ID = uid });
				foreach ( var item in _netObservers ) {
					if ( item.Value.Entities.ContainsKey(uid) ) {
						sc.SendPreformattedMessage(item.Key, message);
						item.Value.Entities.Remove(uid);
					}
				}
			}

			_entityDestroyQueue.Clear();

			foreach ( var entity in _entitySpawnQueue ) {
				if ( entity == null ) {
					continue;
				}
				var uid = entity.ID;
				if ( !_allEntities.ContainsKey(entity.ID) ) {
					_allEntities.Add(uid, entity);
				}					
				//TODO: Rebuild
				var state = entity.Save();
				var message = sc.GetMessageBytes(ServerPacketID.SpawnEntity, new S_SpawnEntityMessage { State = state });
				foreach ( var item in _netObservers ) {
					sc.SendNetMessage(item.Key, ServerPacketID.SpawnEntity, new S_SpawnEntityMessage {State = state});
					item.Value.Entities.Add(uid, entity);
					ChangeEntityObserverCount(uid, 1);
				}
			}

			_entitySpawnQueue.Clear();
		}

		uint GetNewId() {
			_state.EntityIndex++;
			return _state.EntityIndex;
		}

		public void DestroyEntity(uint uid) {
			if ( _entityDestroyQueue.Contains(uid) ) {
				return;
			}
			var e = GetEntityById(uid);
			e.DestroyEntity();
			_entityDestroyQueue.Add(uid);
		}

		public void DestroyEntity(Entity entity) {
			if ( entity == null) {
				return;
			}
			DestroyEntity(entity.ID);
		}

		public Entity CreateNewEntity(EntityType type, Vector3 position, Vector3 rotation, Action<Entity> setupAction) {
			var e = EntityObjectFactory.Instance.CreateEntityObject(type, position, rotation);
			if ( e == null ) {
				return null;
			}
			e.ID = GetNewId();
			setupAction?.Invoke(e);
			e.Create();
			if ( _entityListLock ) {
				_entityCreateQueue.Add(e);
			} else {
				_allEntities.Add(e.ID, e);
			}
			_entitySpawnQueue.Add(e);
			return e;
		}

		public Entity CreateNewEntity(EntityState state, bool replaceID, Action<Entity> setupAction) {
			var position = Vector3.zero;
			var rotation = Vector3.zero;
			var cast = (WorldEntityState)state;
			if ( cast != null) {
				position = cast.Position;
				rotation = cast.Rotation;
			}
			var e = EntityObjectFactory.Instance.CreateEntityObject(state.Type, position, rotation);
			if ( e == null ) {
				return null;
			}
			if ( replaceID) {
				e.ID = GetNewId();
			}
			setupAction?.Invoke(e);
			e.ApplyFullState(state);
			e.Create();
			if ( _entityListLock ) {
				_entityCreateQueue.Add(e);
			} else {
				_allEntities.Add(e.ID, e);
			}			
			_entitySpawnQueue.Add(e);
			return e;
		}

		public void RegisterClient(ClientState client) { //TODO: текущий подход конечно очень кривой, но для прототипа сгодится.
			var state = new PlayerEntityObserver { Client = client};
			var sc = ServerController.Instance;
			//send already spawned entities to new client
			foreach ( var entity in _allEntities ) {
				if ( !entity.Value ) {
					Debug.LogWarning($"ServerEntityController.RegisterClient: when sending to {client.UserName}:{client.ConnectionID} initial world state, found entity with id {entity.Key} which was null or destroyed, but is still in entity list.");
					continue;
				}
				var estate = entity.Value.Save();
				var e = entity.Value;
				sc.SendNetMessage(client, ServerPacketID.SpawnEntity, new S_SpawnEntityMessage {
					State = estate
				});
				state.Entities.Add(entity.Key, e);
				ChangeEntityObserverCount(entity.Key, 1);
			}
			sc.SendNetMessage(client, ServerPacketID.LoadFinalize, new S_LoadFinalizeMessage());
			_netObservers.Add(client, state);

		}

		void ChangeEntityObserverCount(uint id, int change) {
			if ( !_observerCountTracker.TryGetValue(id, out var observer) ) {
				_observerCountTracker.Add(id, change);
			} else {
				_observerCountTracker[id] += change;
			}
		}

		public void UnregisterClient(ClientState client) {
			if ( client == null || !_netObservers.ContainsKey(client) ) {
				return;
			}
			var observer = _netObservers[client];
			if ( observer != null ) {
				foreach ( var e in observer.Entities ) {
					ChangeEntityObserverCount(e.Key, -1);
				}
			}
			_netObservers.Remove(client);
		}

		public void SendEntityPositionUpdate(uint entityId, Vector3 position, Vector3 rotation, bool syncPosition, bool syncRotation) {
			var sc = ServerController.Instance;
			if ( syncPosition && syncRotation ) {
				var bytes = sc.GetMessageBytes(ServerPacketID.UpdateEntityPosRot, new S_SyncTransformMessage() { ID = entityId, Position = position, Rotation = rotation });
				AddRawMessageToSendQueue(entityId, bytes);
			} else if ( syncPosition ) {
				var bytes = sc.GetMessageBytes(ServerPacketID.UpdateEntityPos, new S_SyncPositionMessage() { ID = entityId, Position = position });
				AddRawMessageToSendQueue(entityId, bytes);
			} else if ( syncRotation ) {
				var bytes = sc.GetMessageBytes(ServerPacketID.UpdateEntityRot, new S_SyncRotationMessage() { ID = entityId, Rotation = rotation });
				AddRawMessageToSendQueue(entityId, bytes);
			}
		}

		void AddRawMessageToSendQueue(uint entityId, System.ArraySegment<byte> data) {
			if ( !_entityMessageSendQueue.TryGetValue(entityId, out var q) ) {
				q = new Queue<ArraySegment<byte>>();
				_entityMessageSendQueue.Add(entityId,q);
			}
			q.Enqueue(data);
		}

		void OnClientConnected(OnClientConnected e ) {
			RegisterClient(e.State);
		}

		void OnClientDisconnected(OnClientDisconnected e) {
			UnregisterClient(e.State);
		}
	}

	public sealed class PlayerEntityObserver {
		public ClientState Client = null;
		public Dictionary<uint, Entity> Entities = new Dictionary<uint, Entity>();
		public float LastUpdateTime = 0f;
	}
}
